package org.syntaxlisp.core.utils;

import lombok.Builder;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.beans.PropertyDescriptor;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

/**
 * 自定义 Bean 操作工具类，依赖一些外部工具类
 *
 * @author HuangYijun
 * @data 2020-10-30 11:08
 **/
public class KBeanUtils {

    private KBeanUtils() {

    }

    @Data
    @Builder
    public static class CallRes<T> {

        private String callTime;

        private String returnTime;

        private Long cost;

        private boolean success;

        private Exception thrownExp;

        private T res;
    }

    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    private static final SimpleDateFormat SDF;

    static {
        SDF = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS);
    }

    public static <T> CallRes<T> monitorCall(Supplier<T> call, boolean wrapException) {
        boolean success = true;
        Exception thrownException = null;
        Date beforeCall = new Date();
        T res = null;
        if (wrapException) {
            try {
                res = call.get();
            } catch (Exception e) {
                thrownException = e;
                success = false;
            }
        } else {
            res = call.get();
        }
        Date afterCall = new Date();
        return CallRes.<T>builder()
                      .callTime(SDF.format(beforeCall))
                      .returnTime(SDF.format(afterCall))
                      .cost(afterCall.getTime() - beforeCall.getTime())
                      .thrownExp(thrownException)
                      .success(success)
                      .res(res)
                      .build();
    }

    public static <T> List<T> copyCollectionsToListWithoutTarget(Collection<?> source,
                                                                 Class<T> targetClazz) {
        if (source == null || source.isEmpty()) {
            return Collections.emptyList();
        }
        List<T> target = new ArrayList<>();
        source.forEach(s ->
                               target.add(copyPropertiesWithoutTargetInstance(s,
                                                                              targetClazz)));
        return target;
    }

    /**
     * 基于 copy 源对象以及 copy 目标 <code>Class</code> 得到 copy 目标实例对象
     * <p>这个方法基于 {@code org.springframework.beans.BeanUtils.copyProperties(Object, Object)} 实现
     *
     * @param source      copy 源对象
     * @param targetClazz copy 目标 <code>Class</code>
     * @param <T>         目标类型
     * @return copy 目标实例
     * @throws ParamCopyException
     */
    public static <T> T copyPropertiesWithoutTargetInstance(Object source, Class<T> targetClazz) {
        T target;
        try {
            target = targetClazz.newInstance();
            BeanUtils.copyProperties(source, target);
        } catch (Exception e) {
            throw new ParamCopyException(MessageFormat.format("Object copying error happened when applied KBeanUtils.copyPropertiesWithoutTargetInstance to {0}, exception msg: {1}",
                    targetClazz,
                    e.getMessage()));
        }
        return target;
    }

    /**
     * 基于 copy 源对象以及 copy 目标 <code>Class</code> 得到 copy 目标实例对象，并且忽略 Source 为空的属性值
     *
     * @param source      source object
     * @param targetClazz target object class
     * @param <T>
     * @return target object
     * @throws ParamCopyException
     */
    public static <T> T copyIgnoreNullProperties(Object source, Class<T> targetClazz) {
        T target;
        try {
            target = targetClazz.newInstance();
            copyIgnoreNullProperties(source, target);
        } catch (Exception e) {
            throw new ParamCopyException(MessageFormat.format("Object copying error happened when applied KBeanUtils.copyIgnoreNullProperties to {0}, exception msg: {1}",
                    targetClazz,
                    e.getMessage()));
        }
        return target;
    }

    /**
     * 获取非空 target 值，否则返回默认值
     * @param target target value
     * @param defaultVal default val
     * @param <T>
     * @return 非空 target 值 或 默认值
     */
    public static <T> T getOrDefault(T target, T defaultVal) {
        return target != null ? target : defaultVal;
    }

    public static <T> Optional<T> getOrDefault (Supplier<T> supplier, T defaultVal) {
        return Optional.ofNullable(getOrDefault(supplier.get(), defaultVal));
    }

    public static <T> T getIfCondOrDefault(BooleanSupplier ifCond, Supplier<T> ifCondSupplier, Supplier<T> elseSupplier) {
        if (ifCond.getAsBoolean()) {
            return ifCondSupplier.get();
        } else {
            return elseSupplier.get();
        }
    }

/*    public static <T> T getAndCheckCondOrDefault(Predicate<>, Supplier<T> supplier, Supplier<T> defaultValSupplier) {
        T initVal = supplier.get();
        if (checkCond.getAsBoolean()) {
            return supplier.get();
        } else {
            return defaultValSupplier.get();
        }
    }*/

    public static <T> T getOrThrowException (Supplier<T> supplier, Supplier<? extends RuntimeException> runtimeExpSupplier) {
        T ret = supplier.get();
        if (ret != null) {
            return ret;
        } else {
            throw runtimeExpSupplier.get();
        }
    }

    /**
     * 基于 copy 源对象以及 copy 目标 target，并且忽略 source object 为空的属性值
     *
     * @param source source object
     * @param target target object
     */
    public static void copyIgnoreNullProperties(Object source, Object target) {
        BeanUtils.copyProperties(source, target, acquirePropertiesWhichValueIsNull(source));
    }

    /**
     * 获取空值字段名称
     */
    private static String[] acquirePropertiesWhichValueIsNull(Object obj) {
        final BeanWrapper src = new BeanWrapperImpl(obj);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();
        String[] emptyNames = new String[pds.length];
        int i = 0;
        for (PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames[i] = pd.getName();
                i += 1;
            }
        }
        return Arrays.copyOf(emptyNames, i);
    }

    /**
     * 分组排序
     * @param src 源数据
     * @param groupSorters 分组排序器列表
     * @param <T> 源数据类型
     * @return 分组排序结果
     */
    public static <T> List<T> groupSort (List<T> src, GroupSorter<T> ... groupSorters) {
        // 初始化分组队列，队列中保存分组列表，队列初始只有一个分组
        Queue<List<T>> groups = new ArrayDeque<>();
        groups.add(src);
        // 按照分组排序器列表进行分组排序
        for (GroupSorter<T> groupSorter : groupSorters) {
            int limit = groups.size();
            // 对当前队列中所有组进行分组排序
            for (int i = 0; i < limit; i ++) {
                // 从队列中获取当前处理组
                List<T> currentGroup = groups.poll();
                assert currentGroup != null;
                if (currentGroup.size() > 1) {
                    // 对组内数据进行排序
                    currentGroup.sort(groupSorter.comparator);
                    if (groupSorter.isGroup) {
                        // 对已排序的组数据进行分组
                        List<T> newGroup = new ArrayList<>(Collections.singletonList(currentGroup.get(0)));
                        for (int g = 1; g < currentGroup.size(); g ++) {
                            T candidateGroupItem = currentGroup.get(g);
                            // 通过比较器结果判断是加入当前组，还是创建新组
                            if (groupSorter.comparator.compare(newGroup.get(0), candidateGroupItem) == 0) {
                                newGroup.add(candidateGroupItem);
                            } else {
                                // 旧组加入，新组继续收集
                                groups.add(newGroup);
                                newGroup = new ArrayList<>(Collections.singletonList(candidateGroupItem));
                            }
                        }
                        groups.offer(newGroup);
                    } else {
                        groups.offer(currentGroup);
                    }
                } else {
                    // 当前组内数据量小于等于 1 没有分组排序必要，直接加入分组队列，等待下次分组处理
                    groups.offer(currentGroup);
                }
            }
        }
        // 按首尾连接方式合并分组数据 并 返回
        List<T> ret = new ArrayList<>();
        groups.forEach(ret::addAll);
        return ret;
    }

    /**
     * 分组排序器
     * @param <T> 源数据类型
     */
    public static class GroupSorter<T> {

        /**
         * 排序比较器
         */
        private final Comparator<T> comparator;

        /**
         * 是否按照比较器结果进行分组
         */
        private final boolean isGroup;

        public GroupSorter(Comparator<T> comparator, boolean isGroup) {
            this.comparator = comparator;
            this.isGroup = isGroup;
        }

        public static <T> GroupSorter<T> cons(Comparator<T> comparator, boolean isGroup) {
            return new GroupSorter<>(comparator, isGroup);
        }

        public static <T> GroupSorter<T> cons(Comparator<T> comparator) {
            return new GroupSorter<>(comparator, true);
        }
    }

    public static class ParamCopyException extends RuntimeException {
        public ParamCopyException(String msg) {
            super(msg);
        }
    }
}
